///--------------------------------------------------------------------
/// <file name="namfox-service.tpl.js">
///   NAMFox XPCOM layer implementation.
///
///   The tokens in here (e.g. @stripMarkup@) are expanded at build time.
/// </file>
///--------------------------------------------------------------------

//////////////////////////
// XPCOM Facade
//////////////////////////

function $(selector) {
    /// <summary>
    ///   Retrieves the class object for the specified contract ID.
    /// </summary>
    /// <param name="selector">
    ///   The contract ID for which to retrieve a class object OR
    ///   an interface ID to retrieve the XPCOM interface for.
    /// </param>
    /// <returns type="ComponentClass" />
    
    if (selector) {
        if (selector[0] === "@") {
            return new ComponentClass(selector);
        }
        else {
            return Components.interfaces[selector];
        }
    }
    
    throw new Error("The selector is required for the $() function.");
}

function ComponentClass(contractId) {
    /// <summary>
    ///   Creates a new ComponentClass instance which facilitates
    ///   access to common XPCOM functions.
    /// </summary>
    /// <param name="contractId" type="string">
    ///   The contract ID for which to retrieve a class object.
    /// </param>
    
    this._componentClass = Components.classes[contractId];
    
    if (!this._componentClass) {
        var console = Components.classes["@mozilla.org/consoleservice;1"].getService(
            Components.interfaces.nsIConsoleService
        );
        console.logStringMessage(
            "The contract ID '" +
            (contractId || "NULL") +
            "' did not yield a known component class."
        );
    }
}

ComponentClass.prototype = {
    instance: function(interfaceId) {
        /// <summary>
        ///   Creates an instance of the component given an interface
        ///   nsID for that component to conform to.
        /// </summary>
        /// <param name="interfaceId" type="String">
        ///   The ID of the interface that is returned
        ///   to represent a class instance.
        /// </param>
        /// <returns>
        ///   An instance of a particular class
        ///   given the specified interface ID.
        /// </returns>
        
        return this._componentClass.createInstance(Components.interfaces[interfaceId]);
    },
    
    service: function(interfaceId) {
        /// <summary>
        ///   Gets a reference to a particular XPCOM service based on the
        ///   nsID of an interface for that service to conform to.
        /// </summary>
        /// <param name="interfaceId" type="String">
        ///   The ID of the interface that is returned to represent a service.
        /// </param>
        /// <returns>
        ///   A reference to a particular XPCOM service.
        /// </returns>
        
        return this._componentClass.getService(Components.interfaces[interfaceId]);
    }
};

//////////////////////////
// JSON
//////////////////////////

if(!this.JSON){this.JSON={};}(function(){function f(n){return n<10?'0'+n:n;}
function toJSON(obj,key){if(obj.constructor===Date){return isFinite(obj.valueOf())?obj.getUTCFullYear()+'-'+f(obj.getUTCMonth()+1)+'-'+f(obj.getUTCDate())+'T'+f(obj.getUTCHours())+':'+f(obj.getUTCMinutes())+':'+f(obj.getUTCSeconds())+'Z':null;}else if(obj.constructor===String||obj.constructor===Number||obj.constructor===Boolean){return obj.valueOf();}
return obj;};var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'){value=toJSON(value,key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(){var r="(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)",k='(?:[^\\0-\\x08\\x0a-\\x1f"\\\\]|\\\\(?:["/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';k='(?:"'+k+'*")';var s=new RegExp("(?:false|true|null|[\\{\\}\\[\\]]|"+r+"|"+k+")","g"),t=new RegExp("\\\\(?:([^u])|u(.{4}))","g"),u={'"':'"',"/":"/","\\":"\\",b:"\u0008",f:"\u000c",n:"\n",r:"\r",t:"\t"};function v(h,j,e){return j?u[j]:String.fromCharCode(parseInt(e,16))}
var w=new String(""),x=Object.hasOwnProperty;return function(h,j){h=h.match(s);var e,c=h[0],l=false;if("{"===c)e={};else if("["===c)e=[];else{e=[];l=true}
for(var b,d=[e],m=1-l,y=h.length;m<y;++m){c=h[m];var a;switch(c.charCodeAt(0)){default:a=d[0];a[b||a.length]=+c;b=void 0;break;case 34:c=c.substring(1,c.length-1);if(c.indexOf("\\")!==-1)c=c.replace(t,v);a=d[0];if(!b)if(a instanceof Array)b=a.length;else{b=c||w;break}
a[b]=c;b=void 0;break;case 91:a=d[0];d.unshift(a[b||a.length]=[]);b=void 0;break;case 93:d.shift();break;case 102:a=d[0];a[b||a.length]=false;b=void 0;break;case 110:a=d[0];a[b||a.length]=null;b=void 0;break;case 116:a=d[0];a[b||a.length]=true;b=void 0;break;case 123:a=d[0];d.unshift(a[b||a.length]={});b=void 0;break;case 125:d.shift();break}}
if(l){if(d.length!==1)throw new Error;e=e[0]}else if(d.length)throw new Error;if(j){var p=function(n,o){var f=n[o];if(f&&typeof f==="object"){var i=null;for(var g in f)if(x.call(f,g)&&f!==n){var q=p(f,g);if(q!==void 0)f[g]=q;else{i||(i=[]);i.push(g)}}
if(i)for(g=i.length;--g>=0;)delete f[i[g]]}
return j.call(n,o,f)};e=p({"":e},"")}
return e}}();}}());

//////////////////////////
// Globals
//////////////////////////

$._observerService = $("@mozilla.org/observer-service;1").service("nsIObserverService");
$._windowMediator = $("@mozilla.org/appshell/window-mediator;1").service("nsIWindowMediator");
$.window = function() {
    /// <summary>Gets the most recently active browser nsIDOMWindow.</summary>
    /// <returns type="nsIDOMWindow" />
    
    return $._windowMediator.getMostRecentWindow("navigator:browser");
}

$.version = "@VERSION@";

var hexDigits = "0123456789ABCDEF";
function toHex(dec) {
    /// <summary>Converts a decimal to hexadecimal.</summary>
    /// <param name="dec" type="Number">The decimal to convert.</param>
    
    var hex = hexDigits.substr(dec & 15, 1);
    while (dec > 15) {
        dec >>= 4;
        hex = hexDigits.substr(dec & 15, 1) + hex;
    }
    return hex;
};

$.toRegExp = function(str, flags) {
    /// <summary>Converts a string into a regular expression (RegExp) object.</summary>
    /// <param name="str" type="String">The String object to convert to a RegExp.</param>
    /// <param name="flags" optional="true">(Optional) Flags to pass to the RegExp constructor.</param>
    /// <returns type="RegExp" />
    
    return new RegExp(str.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g,"\\$1"), flags || "");
};

$.repeat = function(original, count) {
    /// <summary>
    ///   Creates a string by repeating the given string
    ///   a certain number of times.
    /// </summary>
    /// <param name="original" type="String">
    ///   The original string to repeat a certain number of times.
    /// </param>
    /// <param name="count" type="Number" integer="true">
    ///   The number of times the original string will occur in the result string.
    /// </param>
    /// <returns type="String" />

    var retVal = [];
    for (var i = 0; i < count; ++i) {
        retVal.push(original);
    }
    return retVal.join("");
};

$.removeLeadingForwardSlash = function(str) {
    /// <summary>Removes a leading forward slash (/) from the specified string, if it exists.</summary>
    /// <param name="str">The string from which to remove the leading forward slash.</param>
    /// <returns type="String" />

    if (!str) {
        return "";
    }

    if ($.startsWith(str, "/")) {
        return str.substring(1);
    }

    return str;
};

$.startsWith = function(str, substring) {
    /// <summary>Determines whether the beginning of this instance matches the specified string.</summary>
    /// <param name="str" type="String">The String object in which to search for the substring.</param>
    /// <param name="substring" type="String">The String object to seek.</param>
    /// <returns type="Boolean" />
    
    return str.indexOf(substring) === 0;
};

$.trim = function(str) {
    /// <summary>Removes all occurrences of white space characters from the beginning and end of this instance.</summary>
    /// <param name="str" type="String">The String object from which to trim whitespace.</param>
    /// <returns type="String" />
    
    return str.replace(/^\s+|\s+$/g, "");
};

/***********************************************************
  Data Structures
***********************************************************/

function Dictionary() { // implements nfxIDictionary
    this._hash = {};
}

Dictionary.prototype = {
    clear: function() {
        /// <summary>Clears the contents of this dictionary.</summary>
        
        this._hash = {};
    },
    
    hasKey: function(key) {
        /// <summary>Returns whether this dictionary has the specified key.</summary>
        /// <param name="key" type="nsISupports">The key to find in the dictionary.</param>
        /// <returns type="boolean" />
    
        return this._hash.hasOwnProperty(key);
    },
    
    getKeys: function(count) {
        /// <summary>Gets all of the keys in this dictionary.</summary>
        /// <param name="count">An output parameter in which to place the number of keys in the return value.</param>
        /// <returns type="array" />
        
        var keys = [];
        for (var key in this._hash) {
            if (this._hash.hasOwnProperty(key)) {
                keys.push(key);
            }
        }
        
        count.value = keys.length;
        
        return keys;
    },
    
    getValue: function(key) {
        /// <summary>Gets the value for the specified key.</summary>
        /// <param name="key" type="String">The key for which to retrieve its corresponding value.</param>
        /// <returns type="nsISupports" />
        
        return this._hash.hasOwnProperty(key) && this._hash[key] || null;
    },
    
    remove: function(key) {
        /// <summary>Removes the specified key from the dictionary.</summary>
        /// <param name="key" type="String">The key to remove.</param>
    
        delete this._hash[key];
    },
    
    setValue: function(key, value) {
        /// <summary>Sets the value given the specified key.</summary>
        /// <param name="key" type="String">The key for the new value.</param>
        /// <param name="value" type="nsISupports">The data to place in the dictionary.</param>
        
        this._hash[key] = value;
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">The requested IID of the interface to try cast to.</param>
        
        if (!iid.equals($("nfxIDictionary")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

function DataRepository() { // implements nfxIDataRepository
    this._cache = $("@namfox.neoseeker.com/dictionary;1").instance("nfxIDictionary");
    
    // Any default data must also be keyed in the $.dataKeys object in chrome/content/utilities/Xpcom.js
    this.setData("QRM", { wrappedJSObject: {} });
}

DataRepository.prototype = {
    getData: function(key) {
        /// <summary>
        ///   Gets arbitrary data from the data repository based on the key.
        /// </summary>
        /// <param name="key" type="String">
        ///   The key of the data to retrieve. The DataKeys variable has
        ///   well-known keys whose values already exist in the repository.
        /// </param>
        /// <returns type="Object" />
        
        if (this._cache.hasKey(key)) {
            return this._cache.getValue(key);
        }
        
        return null;
    },
    
    setData: function(key, data) {
        /// <summary>
        ///   Sets arbitrary data in the data repository based on the key.
        /// </summary>
        /// <param name="key" type="String">
        ///   The key of the data to set. The DataKeys variable has well-known
        ///   keys whose values already exist in the repository.
        /// </param>
        /// <param name="data" type="Object">The data to set.</param>
        
        if (data === null || data === undefined) {
            this._cache.remove(key);
        }
        else {
            this._cache.setValue(key, data);
        }
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIDataRepository")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  AJAX Infrastructure
***********************************************************/

function LocalXMLHttpRequest(xhr) { // implements nfxIXMLHttpRequest
    this._xhr = xhr;
}

LocalXMLHttpRequest.prototype = {
    get responseText() {
        return this._xhr.responseText;
    },
    
    get status() {
        return this._xhr.status;
    },

    parseJSON: function() {
        return JSON.parse(this._xhr.responseText);
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIXMLHttpRequest")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

function Ajax() { // implements nfxIAjax
    $.ajax = this;
    $.get = function(url, data, success, error, cacheHandling, cookie) {
        return $.ajax.get(
            url,
            data,
            success ? { notify: success } : null,
            error ? { notify: error } : null,
            cacheHandling,
            cookie
        );
    };
    $.post = function(url, data, success, error, cacheHandling, cookie) {
        return $.ajax.post(
            url,
            data,
            success ? { notify: success } : null,
            error ? { notify: error } : null,
            cacheHandling,
            cookie
        );
    };
}

Ajax.UseCache = 0;
Ajax.IgnoreCache = 1;

Ajax.prototype = {
    _invoke: function(method, url, postData, success, error, cacheHandling, cookie) {
        /// <summary>Requests data at a specific URL.</summary>
        /// <param name="method" type="String">
        ///   Specifies what method the request will use.
        /// </param>
        /// <param name="url" type="String">
        ///   The URL against which the request will be made.
        /// </param>
        /// <param name="data" type="Object">
        ///   A object of data to send.
        /// </param>
        /// <param name="success" type="Function" optional="true">
        ///   (Optional) The success callback.
        /// </param>
        /// <param name="error" type="Function" optional="true">
        ///   (Optional) The error callback.
        /// </param>
        /// <param name="cacheHandling" type="Number" optional="true">
        ///   (Optional) Specifies how to handle Firefox's cache when making a request.
        /// </param>
        /// <param name="cookie" type="String" optional="true">
        ///   (Optional) A string of cookie data to send with the request.
        /// </param>
        /// <returns type="nsIXMLHttpRequest" />
        
        var xhr = $("@mozilla.org/xmlextras/xmlhttprequest;1").instance("nsIXMLHttpRequest");
        
        if (!url) {
            throw new Components.Exception("You may not get data from or post to a URL which is null or empty.");
        }
        
        // Ensure postData is null when used with GET.
        if (method === "GET") {
            postData = null;
        }
        
        var aborted = false;
        var requestComplete = false, successCalled = false;
        if (success || error) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    requestComplete = true;
                    if (!aborted) {
                        if (xhr.status === 200) {
                            if (success) {
                                try {
                                    successCalled = true;
                                    success.notify(new LocalXMLHttpRequest(xhr));
                                }
                                catch (e) {
                                    $.error(e);
                                }
                            }
                        }
                        else {
                            if (error) {
                                try {
                                    error.notify(new LocalXMLHttpRequest(xhr), false);
                                }
                                catch (e) {
                                    $.error(e);
                                }
                            }
                        }
                    }
                }
            };
        }

        xhr.open(method, url, true);
        
        var nsIRequest = $("nsIRequest");
        if (cacheHandling === Ajax.IgnoreCache) {
            xhr.channel.loadFlags =
                nsIRequest.INHIBIT_PERSISTENT_CACHING |
                nsIRequest.LOAD_BYPASS_CACHE | 
                nsIRequest.LOAD_BACKGROUND;
        } else {
            xhr.channel.loadFlags = nsIRequest.LOAD_FROM_CACHE | nsIRequest.VALIDATE_NEVER;
        }
        
        if (method === "POST") {
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=ISO-8859-1");
        }
        
        // For debugging purposes.
        xhr.setRequestHeader("From", "namfox@neoseeker.com");
        
        if (cookie) {
            $.verbose("CUSTOM COOKIES | Cookie: " + cookie);
            xhr.setRequestHeader("Cookie", cookie);
        }
        
        // Set the timer to a property to avoid it being garbage collected.
        Ajax.timer = $("@mozilla.org/timer;1").instance("nsITimer");
        var abort = {
            notify: function() {
                if (!requestComplete) {
                    aborted = true;

                    if (!requestComplete) {
                        // Aborting the request will cause the
                        // XMLHttpRequest.readyState property to become 4, thus
                        // calling the onreadystatechange event handler. That
                        // handler (see above) uses the value of the aborted
                        // variable to determine whether a timeout occurred.
                        xhr.abort();

                        if (error && !successCalled) {
                            try {
                                // Potential race condition here, but very low chance of it
                                // happening where both success and error callbacks are notified.
                                error.notify(new LocalXMLHttpRequest(xhr), aborted);
                            }
                            catch (e) {
                                $.error(e);
                            }
                        }
                    }
                }
            }
        };
        
        // UNDONE: Should the 20-second timeout be configurable?
        Ajax.timer.initWithCallback(abort, 20000, $("nsITimer").TYPE_ONE_SHOT);
        $.verbose("Making " + method + " request to " + url);
        xhr.send(postData);
        
        return new LocalXMLHttpRequest(xhr);
    },
    
    _param: function(data) {
        /// <summary>
        ///   Turns an object into key/value pairs for sending as a URL.
        /// </summary>
        /// <param name="data">The object to parse.</param>
        
        if (data.wrappedJSObject) {
            data = data.wrappedJSObject;
        }
        
        var a = [];
        for (var p in data) {
            if (data.hasOwnProperty(p) && typeof(data[p]) !== "function") {
                a.push(p + "=" + data[p]);
            }
        }
        
        return a.join("&").replace(/ /g, "+");
    },
    
    get: function(url, data, success, error, cacheHandling, cookie) {
        /// <summary>Gets data at a specific URL.</summary>
        /// <param name="url" type="String">
        ///   The URL against which the request will be made.
        /// </param>
        /// <param name="data" type="Object">
        ///   A object of data to send.
        /// </param>
        /// <param name="success" type="Function" optional="true">
        ///   (Optional) The success callback.
        /// </param>
        /// <param name="error" type="Function" optional="true">
        ///   (Optional) The error callback.
        /// </param>
        /// <param name="cacheHandling" type="Number" optional="true">
        ///   (Optional) Specifies how to handle Firefox's cache when making a request.
        /// </param>
        /// <param name="cookie" type="String" optional="true">
        ///   (Optional) A string of cookie data to send with the request.
        /// </param>
        /// <returns type="nsIXMLHttpRequest" />
        
        if (data && url) {
            var parameters = this._param(data);
            
            if (parameters) {
                url = url + "?" + parameters;
            }
        }
        
        return this._invoke("GET", url, null, success, error, cacheHandling, cookie);
    },
    
    post: function(url, data, success, error, cacheHandling, cookie) {
        /// <summary>Posts data to a specific URL.</summary>
        /// <param name="url" type="String">
        ///   The URL against which the request will be made.
        /// </param>
        /// <param name="data" type="Object">
        ///   A object of data to send.
        /// </param>
        /// <param name="success" type="Function" optional="true">
        ///   (Optional) The success callback.
        /// </param>
        /// <param name="error" type="Function" optional="true">
        ///   (Optional) The error callback.
        /// </param>
        /// <param name="cacheHandling" type="Number" optional="true">
        ///   (Optional) Specifies how to handle Firefox's cache when making a request.
        /// </param>
        /// <param name="cookie" type="String" optional="true">
        ///   (Optional) A string of cookie data to send with the request.
        /// </param>
        /// <returns type="nsIXMLHttpRequest" />
        
        return this._invoke("POST", url, this._param(data), success, error, cacheHandling, cookie);
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIAjax")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  Custom Message Infrastructure
***********************************************************/

function CustomMessage() { // implements nfxICustomMessage
}

CustomMessage.prototype = {
    _text: null,
    
    get text() {
        /// <summary>Gets the value of this custom message.</summary>
        /// <returns type="String" />
        
        return this._text;
    },
    
    set text(value) {
        /// <summary>Sets the value of this custom message.</summary>
        /// <param name="value" type="String">
        ///   The new value of the custom message.
        /// </param>
        
        this._text = value;
        $._observerService.notifyObservers(this, "custom-message-text-changed", null);
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxICustomMessage")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

function NamedCustomMessage() { // extends CustomMessage implements nfxINamedCustomMessage
}

NamedCustomMessage.prototype = new CustomMessage();

NamedCustomMessage.prototype._name = null;
NamedCustomMessage.prototype.__defineGetter__(
    "name",
    function() {
        return this._name;
    }
);
NamedCustomMessage.prototype.__defineSetter__(
    "name",
    function(value) {
        this._name = value;
        $._observerService.notifyObservers(this, "custom-message-name-changed", null);
    }
);
NamedCustomMessage.prototype.QueryInterface = function(iid) {
    /// <summary>Provides runtime type discovery.</summary>
    /// <param name="iid" type="nsIIDRef">
    ///   The requested IID of the interface to try cast to.
    /// </param>
    
    if (!iid.equals($("nfxINamedCustomMessage")) &&
        !iid.equals($("nfxICustomMessage")) &&
        !iid.equals($("nsISupports"))) {
        throw Components.results.NS_ERROR_NO_INTERFACE;
    }
    
    return this;
}

function CustomMessageManager() { // implement nfxICustomMessageManager
    this._messages = $("@namfox.neoseeker.com/dictionary;1").instance("nfxIDictionary");
    this._defaultMessage = new CustomMessage();
    this._defaultMessage.key = "defaultMessage";
    this._defaultMessage.text = "";
    
    this._profileDirectory = $("@mozilla.org/file/directory_service;1").
        service("nsIProperties").get("ProfD", $("nsIFile")).path;
    
    // Different operating systems use different directory separators.
    this._pathSeparator = this._profileDirectory.search(/\\/) === -1 ? "/" : "\\";
    this._profileDirectory += this._pathSeparator;
    
    this._customMessageFilePath = this._profileDirectory + "namfox.ncm";
    
    this._init();
}

CustomMessageManager.prototype = {
    // Private interface
    _convertFromJson: function(jsonString) {
        /// <summary>
        ///   Converts the custom messages, serialized
        ///   as JSON, to our nfxIDictionary.
        /// </summary>
        /// <param name="jsonString" type="String">
        ///   The string of JSON to parse.
        /// </param>
        
        var messages = JSON.parse(jsonString);
        
        for (var key in messages) {
            if (messages.hasOwnProperty(key)) {
                var message = messages[key];
                
                if (message) {
                    // We set the properties of the message after adding it
                    // to the _messages dictionary in order for other code to have
                    // access to the message in the dictionary when observers fire.
                    if (key !== "defaultMessage") {
                        newMessage = new NamedCustomMessage();
                        newMessage.key = key;
                        this._messages.setValue(key, newMessage.QueryInterface($("nsISupports")));
                        newMessage.name = message.name;
                    }
                    else {
                        newMessage = this._defaultMessage;
                    }
                    newMessage.text = message.message;
                }
                else if (key !== "defaultMessage") {
                    newMessage = new NamedCustomMessage();
                    this._messages.setValue(key, newMessage.QueryInterface($("nsISupports")));
                }
            }
        }
        
        var count = {};
        this._messages.getKeys(count);
        count = count.value;
        if (count < 25) {
            for (var i = count + 1; i < 26; ++i) {
                var msg = new NamedCustomMessage();
                msg.key = i.toString();
                msg.name = "Custom Message " + i;
                this._messages.setValue(i.toString(), msg.QueryInterface($("nsISupports")));
            }
        }
    },
    
    _convertToJson: function() {
        /// <summary>Converts the custom messages stored in the nsIDictionary to JSON.</summary>
        /// <returns type="string" />
        
        var messages = {}, keyCount = {};
        var keys = this._messages.getKeys(keyCount);
        
        // Iterate over the keys from the manager and put them into the messages object.
        // We'll then serialize the messages object into the JSON format.
        for (var i = 0; i < keyCount.value; ++i) {
            var key = keys[i];
            var message = this._messages.getValue(key).QueryInterface($("nfxINamedCustomMessage"));
            messages[key] = {
                name: message.name || "Custom Message" + (i + 1).toString(),
                message: message.text
            };
        }
        messages["defaultMessage"] = { name: "Default", message: this._defaultMessage.text };
        
        return JSON.stringify(messages);
    },
    
    _init: function() {
        /// <summary>Initializes the CustomMessageManager.</summary>
        
        this._initialized = true;
        
        var currentFile = $("@mozilla.org/file/local;1").instance("nsILocalFile");
        currentFile.initWithPath(this._customMessageFilePath);
        
        // If namfox.ncm exists, then parse the JSON and load the data.
        if (currentFile.exists()) {
            this._load(this._customMessageFilePath);
        }
        else {
            for (var i = 1; i < 26; ++i) {
                var message = new NamedCustomMessage();
                var messageName = this._readFile(this._profileDirectory + "cn" + i + ".xml");
                var messageValue = this._readFile(this._profileDirectory + "cm" + i + ".xml");
                var key = i.toString();
                
                message.key = key;
                message.name = messageName || "Custom Message " + key;
                message.text = messageValue || "";
                this._messages.setValue(key, message.QueryInterface($("nsISupports")));
            }
            
            messageValue = this._readFile(this._profileDirectory + "cm16.xml"); // Default message
            if (messageValue) {
                this._defaultMessage.text = messageValue;
            }
            
            // Save the custom messages into the new format.
            this.save();
        }
    },
    
    _load: function(filePath) {
        /// <summary>
        ///   Loads a JSON-serialized file with custom message data.
        /// </summary>
        /// <param name="filePath" type="String">
        ///   The path of the file to read.
        /// </param>
        
        var messageContents = this._readFile(filePath);
        if (messageContents) {
            try {
                this._convertFromJson(messageContents);
            }
            catch (e) {
                $.error(e);
            }
        }
    },
    
    _readFile: function(filePath) {
        /// <summary>Reads a file with the given file name.</summary>
        /// <param name="filePath" type="String">
        ///   The full path of the file to read.
        /// </param>
        
        var file = $("@mozilla.org/file/local;1").instance("nsILocalFile");
        
        file.initWithPath(filePath);
        
        if (file.exists()) {
            var PR_RDONLY = 0x01;
            var inputStream = $("@mozilla.org/network/file-input-stream;1").
                instance("nsIFileInputStream");
            inputStream.init(
                file,
                PR_RDONLY,
                null,
                $("nsIFileInputStream").CLOSE_ON_EOF
            );
            
            var unicodeConverter = $("@mozilla.org/intl/scriptableunicodeconverter").
                instance("nsIScriptableUnicodeConverter");
            unicodeConverter.charset = "UTF-8";
            
            var scriptableInputStream = $("@mozilla.org/scriptableinputstream;1").
                instance("nsIScriptableInputStream");
            scriptableInputStream.init(inputStream);
            
            var rawText = scriptableInputStream.read(scriptableInputStream.available());
            
            return unicodeConverter.ConvertToUnicode(rawText);
        }
        
        return null;
    },
    
    _saveCore: function(filePath) {
        /// <summary>Saves a JSON-serialized file with custom message data to the file at the specified path.</summary>
        /// <param name="filePath" type="string">The path of the file where the custom message data should be saved.</param>
        
        var PR_WRONLY = 0x02, PR_TRUNCATE = 0x20, READ_BY_OTHERS = 04;
        
        var file = $("@mozilla.org/file/local;1").instance("nsILocalFile");
        var encoder = $("@mozilla.org/intl/scriptableunicodeconverter").
            instance("nsIScriptableUnicodeConverter");
        var streamWriter = $("@mozilla.org/network/file-output-stream;1").
            instance("nsIFileOutputStream");
        
        try {
            file.initWithPath(filePath);
            if (!file.exists()) {
                // 0664 = PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IWGRP | PR_IROTH;
                // File ACLs: Read/write owner, Read/write group, Read by others.
                file.create($("nsILocalFile").NORMAL_FILE_TYPE, 0664);
            }
            
            encoder.charset = "UTF-8";
            var data = encoder.ConvertFromUnicode(this._convertToJson());
            
            streamWriter.init(file, PR_TRUNCATE | PR_WRONLY, READ_BY_OTHERS, null);
            streamWriter.write(data, data.length);
            
            endCharacter = encoder.Finish();
            if (endCharacter.length > 0) {
                streamWriter.write(endCharacter, endCharacter.length);
            }
            
            streamWriter.flush();
        }
        catch (e) {
            $.error(e);
        }
        finally {
            streamWriter.close();
        }
    },
    
    // Public interface
    get defaultMessage() {
        return this._defaultMessage;
    },
    
    get messages() {
        return this._messages;
    },
    
    backup: function(filePath) {
        /// <summary>
        ///   Backs up the custom messages to a location specified by the user.
        /// </summary>
        /// <param name="filePath" type="String" optional="true">
        ///   (Optional) The file path where custom messages will be saved.
        /// </param>
        
        if (!filePath) {
            var filePicker = $("@mozilla.org/filepicker;1").instance("nsIFilePicker");
            filePicker.init($.window(), "Backup Where?", $("nsIFilePicker").modeSave);
            filePicker.defaultExtension = "*.ncm";
            filePicker.defaultString = "NAMFox Message Backup.ncm";
            filePicker.appendFilter("NAMFox Custom Message File (*.ncm)", "*.ncm");
            
            var dialogResult = filePicker.show();
            if (dialogResult === $("nsIFilePicker").returnOK ||
                dialogResult === $("nsIFilePicker").returnReplace) {
                filePath = filePicker.file.path;
            }
        }
        
        if (filePath) {
            this._saveCore(filePath);
        }
    },
    
    refresh: function() {
        /// <summary>
        ///   Reloads custom messages from the default custom message file.
        /// </summary>
        
        this._load(this._customMessageFilePath);
    },
    
    restore: function(filePath) {
        /// <summary>
        ///   Backs up the custom messages to a location specified by the user.
        /// </summary>
        /// <param name="filePath" type="String">
        ///   The file path from which custom messages will be restored.
        /// </param>
        
        if (!filePath) {
            var filePicker = $("@mozilla.org/filepicker;1").instance("nsIFilePicker");
            filePicker.init($.window(), "Restore From Where?", $("nsIFilePicker").modeOpen);
            filePicker.defaultExtension = "*.ncm";
            filePicker.defaultString = "NAMFox Message Backup.ncm";
            filePicker.appendFilter("NAMFox Custom Message File (*.ncm)", "*.ncm");
            
            var dialogResult = filePicker.show();
            if (dialogResult == $("nsIFilePicker").returnOK) {
                filePath = filePicker.file.path;
            }
        }
        
        if (filePath) {
            this._load(filePath);
        }
        
        // Re-save over new custom messages.
        this.save();
    },
    
    save: function() {
        /// <summary>Saves the most recent custom message data to disk.</summary>
    
        this._saveCore(this._customMessageFilePath);
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxICustomMessageManager")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  Neoseeker Member Services
***********************************************************/

function Member() { // implements nfxIMember
}

Member.prototype = {
    _customSmileyUrls: [],
    _isModerator: false,
    _userName: null,
    
    getCustomSmileyUrls: function(count) {
        count.value = this._customSmileyUrls.length;

        return this._customSmileyUrls;
    },
    
    get isModerator() {
        return this._isModerator;
    },
    
    get userName() {
        return this._userName;
    },
    
    set userName(value) {
        if (this._userName !== value) {
            this._userName = value;
            $._observerService.notifyObservers(this, "neoseeker-user-name-changed", null);
        }
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIMember")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

function MemberService() {
    $._observerService.addObserver(this, "http-on-examine-response", false);
    $._observerService.addObserver(this, "quit-application", false);
}

MemberService.prototype = {
    _member: new Member(),
    _cookieService: $("@mozilla.org/cookieService;1").service("nsICookieService"),
    
    observe: function(subject, topic, data) {
        /// <summary>
        ///   Called when there is a notification for the topic
        ///   that the observer has been registered for.
        /// </summary>
        /// <param name="subject" type="nsISupports">
        ///   The object whose change or action is being observed.
        /// </param>
        /// <param name="topic" type="String">
        ///   Indicates the specific change or action.</param>
        /// <param name="data" type="String" optional="true">
        ///   (Optional) Auxiliary data further describing the change or action.
        /// </param>
        
        if (topic === "quit-application") {
            $._observerService.removeObserver(this, "http-on-examine-response");
            $._observerService.removeObserver(this, "quit-application");
        }
        else if (topic === "http-on-examine-response") {
            subject = subject.QueryInterface($("nsIHttpChannel"));
            var prePath = subject.URI.prePath;
            
#ifdef DEV
            if (prePath === "http://beta.neoseeker.com") {
#else
            if (prePath === "http://neoseeker.com" || prePath === "http://www.neoseeker.com") {
#endif
                if ($.config.diagnostics.traceRequests) {
                    $.verbose("===========================NEW REQUEST===========================");
                    $.verbose("Inside " + subject.URI.spec);
                }

                var cookies = this._cookieService.getCookieString(subject.URI, subject);
                
                // HACK: In some requests (e.g. requesting the reply message
                // during the quick reply load), the cookie service returns
                // null for the cookie string. Since we have not actually
                // logged out, it is better not to parse it.
                if (!cookies) {
                    cookies = "";
                }
                
                try {
                    var responseCookies = subject.getResponseHeader("Set-Cookie");
                }
                catch (e) {
                    responseCookies = "";
                }

                if ($.config.diagnostics.traceRequests) {
                    $.verbose("Existing Cookies : " + cookies);
                    $.verbose("Response cookies : " + responseCookies);
                }
                
                // First, check if Neoseeker has sent us a neologin cookie.
                // If so, we use that.
                var results = responseCookies.match(/neologin=(.*?)(?:;|$)/);
                var userName;
                if (results) {
                    userName = results[1];
                    $.verbose("User name found : " + userName);
                }
#ifdef DEV
                else if (responseCookies.indexOf("neoseeker_perm_auth_dev=deleted") !== -1) {
#else
                else if (responseCookies.indexOf("neoseeker_perm_auth=deleted") !== -1) {
#endif
                    // This implies an explicit log out.
                    userName = null;
                    $.verbose("User logged out!");

                    var cookieManager = $("@mozilla.org/cookiemanager;1").service("nsICookieManager");
                    cookieManager.remove(".neoseeker.com", "neologin", "/", false);

                    $.verbose("Removed neologin cookie...");
                }
                else if (cookies && (!responseCookies || responseCookies.indexOf("lastvisit=deleted") === -1)) {
                    results = cookies.match(/neologin=(.*?)(?:;|$)/);
                    
                    if (results) {
                        userName = results[1];
                        $.verbose("Found user name through cookies: " + cookies + " | " + responseCookies);

                        if ($.config.diagnostics.traceRequests) {
                             var cookieManager = $("@mozilla.org/cookiemanager;1").service("nsICookieManager");
                             var cookies = cookieManager.enumerator;

                             while (cookies.hasMoreElements()) {
                                 var cookie = cookies.getNext();
        
                                 cookie = cookie.QueryInterface($("nsICookie"));

                                 if (cookie.host.search(/seeker/) !== -1) {
                                     $.verbose("        " + cookie.host + " | " + cookie.name + " | " + cookie.path);
                                 }
                             }
                         }

                    }
                    else {
                        return;
                    }
                }
                else {
                    return;
                }

                userName = (userName && userName.replace(/\+/g, " ")) || null;
                $.verbose("User name: " + userName);

                if (userName !== this._member.userName) {
                    this._member.userName = userName;
                    $.info("MEMBER | User Name Change: " + userName);
                    
                    if (!userName) {
                        this._member._isModerator = false;
                        this._member._customSmileyUrls = [];
                        return;
                    }
                    
                    responseCookies = responseCookies.replace(/ (?:expires|path|domain)=.*?(;|$|\n)/g, "").replace(/\n/g, ";");
                    var that = this;
                    $.get(
                        $.url.api("/user/is_moderator.json"),
                        null,
                        function (xhr) {
                            var response = xhr.parseJSON();
                            $.verbose("Response from is_moderator: " + response.toSource());
                            that._member._isModerator = response;
                        },
                        function (xhr, causedByTimeout) {
                            // Err on the side of caution here. If the request
                            // fails ensure their permissions are revoked.
                            that._member._isModerator = false;
                        },
                        $("nfxIAjax").BypassCache
                    );

                    this.updateCustomSmileys();
                }
            }
        }
    },

    getCurrentUser: function() {
        /// <summary>Gets the member that is currently logged on.</summary>
        
        return this._member;
    },
    
    refreshCurrentUser: function(subsequentTry) {
        /// <summary>
        ///   Attempts to locate the currently signed in user.
        /// </summary>
        
        $.verbose("USER REFRESH | Old user name: " + this._member._userName);
        
        if (this._member._userName) {
            return;
        }
        
        var that = this;
        $.get(
            $.url.member("/edit_profile.html?edit=user_profile"),
            null,
            function (xhr) {
                $.verbose("USER REFRESH | Got results");
                var results = xhr.responseText.match(/Editing Profile for (.*?)<\/b>/);
                
                if (results) {
                    that._member.userName = results[1];
                    $.verbose("USER REFRESH | New user name: " + results[1]);
                }
            },
            function (xhr, causedByTimeout) {
                if (!subsequentTry) {
                    that.refreshCurrentUser(true);
                }
            },
            $("nfxIAjax").BypassCache
        );
    },

    updateCustomSmileys: function() {
        var that = this;
        $.get(
            $.url.api("/user/get_custom_smilies.json"),
            null,
            function (xhr) {
                var response = xhr.parseJSON();

                $.verbose("Response from get_custom_smilies: " + response.toSource());

                if (response.length > 0) {

                    that._member._customSmileyUrls = [];
                    for (var i = 0; i < response.length; ++i) {
                        $.verbose("Found smiley URL : " + response[i].url);
                        that._member._customSmileyUrls[i] = response[i].url;
                    }
                }
            },
            null,
            $("nfxIAjax").BypassCache
        );
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIMemberService")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  Configuration
***********************************************************/

function PrefUpdater() { // implements nfxIPrefUpdater
}

PrefUpdater.prototype = {
    _prefService: $("@mozilla.org/preferences-service;1").service("nsIPrefService")
};

function PrefBranch(prefBranchName) { // implements nfxIPrefBranch
    this._prefBranch = this._prefService.getBranch(prefBranchName).QueryInterface($("nsIPrefBranch2"));
}

PrefBranch.defineConfigurationProperty = function(proto, propertyName, defaultValue, prefName) {
    /// <summary>
    ///   Defines a configuration property with a getter and
    ///   setter for the given prefName.
    /// </summary>
    /// <param name="proto" type="Object">
    ///   The prototype of the object on which these properties will be defined.
    /// </param>
    /// <param name="propertyName type="String">
    ///   The name of the property to define.
    /// </param>
    /// <param name="defaultValue type="Object">
    ///   The default value for this preference, if it can't be found.
    /// </param>
    /// <param name="prefName" type="String" optional="true">
    ///   (Optional) The name of the preference to get and set
    ///   for this property. Default is the propertyName.
    /// </param>
    
    var getter, setter;
    
    switch (typeof(defaultValue)) {
        case "boolean":
            if (!proto.getBoolPref || !proto.setBoolPref) {
                throw new Error("The prototype must have getBoolPref and setBoolPref functions.");
            }
            getter = proto.getBoolPref;
            setter = proto.setBoolPref;
            break;
        case "number":
            if (!proto.getIntPref || !proto.setIntPref) {
                throw new Error("The prototype must have getIntPref and setIntPref functions.");
            }
            getter = proto.getIntPref;
            setter = proto.setIntPref;
            break;
        case "string":
            if (!proto.getCharPref || !proto.setCharPref) {
                throw new Error("The prototype must have getCharPref and setCharPref functions.");
            }
            getter = proto.getCharPref;
            setter = proto.setCharPref;
            break;
        default:
            throw new Error(
                "Could not determine the type of the default value," +
                " and therefore could not generate a configuration property."
            );
    }
    
    if (!prefName) {
        prefName = propertyName;
    }
    
    proto.__defineGetter__(
        propertyName,
        function() {
            if (propertyName) {
//                $.window().alert(propertyName);
            }
            return getter.call(proto, prefName, defaultValue);
        }
    );
    proto.__defineSetter__(
        propertyName,
        function(value) {
            setter.call(proto, prefName, value);
        }
    );
};

PrefBranch.prototype = {
    _prefService: $("@mozilla.org/preferences-service;1").service("nsIPrefService"),
    
    addObserver: function(domain, observer, holdWeak) {
        /// <summary>
        /// Adds a preference change observer.
        /// </summary>
        /// <param name="domain" type="String">
        ///   The preference on which to listen for changes. This can be the
        ///   name of an entire branch to observe. e.g. Holding the "root"
        ///   prefbranch and calling addObserver("foo.bar.", ...) will
        ///   observe changes to foo.bar.baz and foo.bar.bzip.
        /// </param>
        /// <param name="observer" type="nsIObserver">
        ///   The object to be notified if the preference changes.
        /// </param>
        /// <param name="holdWeak" type="Boolean">
        ///   If true, holds a weak reference to the observer. The object must
        ///   implement the nsISupportsWeakReference interface or this will
        ///   fail. If false, holds a strong reference to the observer.
        /// </param>
        
        this._prefBranch.addObserver(domain, observer, holdWeak);
    },
    
    getBoolPref: function(prefName, defaultValue) {
        /// <summary>
        ///   Called to get the state of an individual boolean preference.
        /// </summary>
        /// <param name="prefName" type="String">
        ///   The name of the preference to retrieve.
        /// </param>
        /// <param name="defaultValue" type="Boolean">
        ///   The default value if the preference cannot be retrieved.
        /// </param>
        /// <returns type="Boolean" />
        
        try {
            return this._prefBranch.getBoolPref(prefName);
        }
        catch (e) {
            $.error(e);
            return defaultValue;
        }
    },
    
    getCharPref: function(prefName, defaultValue) {
        /// <summary>
        ///   Called to get the state of an individual string preference.
        /// </summary>
        /// <param name="prefName" type="String">
        ///   The name of the preference to retrieve.
        /// </param>
        /// <param name="defaultValue" type="String">
        ///   The default value if the preference cannot be retrieved.
        /// </param>
        /// <returns type="string" />
        
        try {
            return this._prefBranch.getCharPref(prefName);
        }
        catch (e) {
            $.error(e);
            return defaultValue;
        }
    },
    
    getIntPref: function(prefName, defaultValue) {
        /// <summary>
        ///   Called to get the state of an individual string preference.
        /// </summary>
        /// <param name="prefName" type="String">
        ///   The name of the preference to retrieve.
        /// </param>
        /// <param name="defaultValue" type="Number" integer="true">
        ///   The default value if the preference cannot be retrieved.
        /// </param>
        /// <returns type="Number" integer="true" />
        
        try {
            return this._prefBranch.getIntPref(prefName);
        }
        catch (e) {
            $.error(e);
            return defaultValue;
        }
    },
    
    removeObserver: function(domain, observer) {
        /// <summary>
        ///   Removes a preference change observer.
        /// </summary>
        /// <param name="domain" type="String">
        ///   The preference which is being observed for changes.
        /// </param>
        /// <param name="observer" type="nsIObserver">
        ///   The object that was notified if the preference changes.
        /// </param>
        
        this._prefBranch.removeObserver(domain, observer);
    },
    
    setBoolPref: function(prefName, value) {
        /// <summary>
        ///   Called to set the state of an individual boolean preference.
        /// </summary>
        /// <param name="prefName" type="String">
        ///   The name of the preference to set the state of.
        /// </param>
        /// <param name="value" type="Boolean">
        ///   The boolean value to set the preference to.
        /// </param>
        
        try {
            this._prefBranch.setBoolPref(prefName, value);
        }
        catch (e) {
            $.error(e);
        }
    },
    
    setCharPref: function(prefName, value) {
        /// <summary>
        ///   Called to set the state of an individual string preference.
        /// </summary>
        /// <param name="prefName" type="String">
        ///   The name of the preference to set the state of.
        /// </param>
        /// <param name="value" type="String">
        ///   The string value to set the preference to.
        /// </param>
        
        try {
            this._prefBranch.setCharPref(prefName, value);
        }
        catch (e) {
            $.error(e);
        }
    },
    
    setIntPref: function(prefName, value) {
        /// <summary>
        ///   Called to set the state of an individual int preference.
        /// </summary>
        /// <param name="prefName" type="String">
        ///   The name of the preference to set the state of.
        /// </param>
        /// <param name="value" type="Number" integer="true">
        ///   The int value to set the preference to.
        /// </param>
        
        try {
            this._prefBranch.setIntPref(prefName, value);
        }
        catch (e) {
            $.error(e);
        }
    },
};

////////////////////////////////
// AutoComplete Configuration
////////////////////////////////

function AutoCompleteBranch() { // extends PrefBranch implements nfxIAutoCompleteBranch
}

AutoCompleteBranch.prototype = new PrefBranch("extensions.namfox.autoComplete.");
PrefBranch.defineConfigurationProperty(AutoCompleteBranch.prototype, "enabled", @autocomplete.enabled@);
PrefBranch.defineConfigurationProperty(AutoCompleteBranch.prototype, "onDemandOnly", @autocomplete.onDemandOnly@);

////////////////////////////////
// Diagnostics Configuration
////////////////////////////////

function DiagnosticsBranch() { // extends PrefBranch implements nfxIDiagnosticsBranch
}

DiagnosticsBranch.prototype = new PrefBranch("extensions.namfox.diagnostics.");
PrefBranch.defineConfigurationProperty(DiagnosticsBranch.prototype, "assertUIEnabled", @diagnostics.assertUIEnabled@);
PrefBranch.defineConfigurationProperty(DiagnosticsBranch.prototype, "exceptionUIEnabled", @diagnostics.exceptionUIEnabled@);
PrefBranch.defineConfigurationProperty(DiagnosticsBranch.prototype, "traceLevel", @diagnostics.traceLevel@);
PrefBranch.defineConfigurationProperty(DiagnosticsBranch.prototype, "traceRequests", @diagnostics.traceRequests@);

////////////////////////////////
// Markup Configuration
////////////////////////////////

function MarkupBranch() { // extends PrefBranch implements nfxIMarkupBranch
}

MarkupBranch.prototype = new PrefBranch("extensions.namfox.buttons.");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "anchor", @markup.anchor@, "Anchor");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "code", @markup.code@, "Code");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "customMessages", @markup.customMessages@, "CustomMessages");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "horizontalRule", @markup.horizontalRule@, "HorizontalRule");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "indent", @markup.indent@, "Indent");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "privateMessageLink", @markup.pmLink@, "PMLink");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "privateMessageShortHand", @markup.pmShort@, "PMShort");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "php", @markup.php@, "Php");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "pre", @markup.pre@, "Pre");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "profile", @markup.profile@, "Profile");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "replace", @markup.replace@, "Replace");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "strike", @markup.strike@, "Strike");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "subscript", @markup.subscript@, "Subscript");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "superscript", @markup.superscript@, "Superscript");
PrefBranch.defineConfigurationProperty(MarkupBranch.prototype, "videos", @markup.videos@, "Videos");

////////////////////////////////
// Post Configuration
////////////////////////////////

function StripMarkupBranch() { // extends PrefBranch implements nfxIStripMarkupBranch
}

StripMarkupBranch.prototype = new PrefBranch("extensions.namfox.stripMarkup.");
PrefBranch.defineConfigurationProperty(StripMarkupBranch.prototype, "colorLightness", @stripMarkup.colorLightness@);
PrefBranch.defineConfigurationProperty(StripMarkupBranch.prototype, "stripOptions", @stripMarkup.stripOptions@);

////////////////////////////////
// Private Message Configuration
////////////////////////////////

function PrivateMessageBranch() { // extends PrefBranch implements nfxIPrivateMessageBranch
}

PrivateMessageBranch.prototype = new PrefBranch("extensions.namfox.sendPM.");
PrefBranch.defineConfigurationProperty(PrivateMessageBranch.prototype, "readReceipt", @privateMessages.readReceipt@);

////////////////////////////////
// Quick Edit Configuration
////////////////////////////////

function QuickEditBranch() { // extends PrefBranch implements nfxIQuickEditBranch
}

QuickEditBranch.prototype = new PrefBranch("extensions.namfox.quickEdit.");
PrefBranch.defineConfigurationProperty(QuickEditBranch.prototype, "doubleClickEnabled", @quickEdit.doubleClickEnabled@);
PrefBranch.defineConfigurationProperty(QuickEditBranch.prototype, "disableDoubleClickForPostsWithMedia", @quickEdit.disableDoubleClickForPostsWithMedia@);

////////////////////////////////
// Quick Reply Configuration
////////////////////////////////

function QuickReplyBranch() { // extends PrefBranch implements nfxIQuickReplyBranch
}

QuickReplyBranch.prototype = new PrefBranch("extensions.namfox.quickReply.");
PrefBranch.defineConfigurationProperty(QuickReplyBranch.prototype, "enabled", @quickReply.enabled@);
PrefBranch.defineConfigurationProperty(QuickReplyBranch.prototype, "rememberMessage", @quickReply.rememberMessage@);
PrefBranch.defineConfigurationProperty(QuickReplyBranch.prototype, "textAreaHeight", @quickReply.textAreaHeight@);
PrefBranch.defineConfigurationProperty(QuickReplyBranch.prototype, "textAreaWidth", "@quickReply.textAreaWidth@");
PrefBranch.defineConfigurationProperty(QuickReplyBranch.prototype, "useSignatureChecked", @quickReply.useSignatureChecked@);

////////////////////////////////
// Toolbar Configuration
////////////////////////////////

function ToolbarBranch() { // extends PrefBranch implements nfxIToolbarBranch
}

ToolbarBranch.prototype = new PrefBranch("extensions.namfox.toolbar.");
PrefBranch.defineConfigurationProperty(ToolbarBranch.prototype, "enabledSmileys", "@toolbar.enabledSmileys@");
PrefBranch.defineConfigurationProperty(ToolbarBranch.prototype, "linkBehavior", @toolbar.linkBehavior@);

////////////////////////////////
// NAMFox Configuration
////////////////////////////////

function Configuration() { // extends PrefBranch implements nfxIConfiguration
    $.config = this;
}

Configuration.prototype = new PrefBranch("extensions.namfox.");

Configuration.prototype._autoComplete = new AutoCompleteBranch();
Configuration.prototype.__defineGetter__("autoComplete", function() { return this._autoComplete; });

Configuration.prototype._diagnostics = new DiagnosticsBranch();
Configuration.prototype.__defineGetter__("diagnostics", function() { return this._diagnostics; });

Configuration.prototype._markup = new MarkupBranch();
Configuration.prototype.__defineGetter__("markup", function() { return this._markup; });

Configuration.prototype._stripMarkup = new StripMarkupBranch();
Configuration.prototype.__defineGetter__("stripMarkup", function() { return this._stripMarkup; });

Configuration.prototype._privateMessages = new PrivateMessageBranch();
Configuration.prototype.__defineGetter__("privateMessages", function() { return this._privateMessages; });

Configuration.prototype._quickEdit = new QuickEditBranch();
Configuration.prototype.__defineGetter__("quickEdit", function() { return this._quickEdit; });

Configuration.prototype._quickReply = new QuickReplyBranch();
Configuration.prototype.__defineGetter__("quickReply", function() { return this._quickReply; });

Configuration.prototype._toolbar = new ToolbarBranch();
Configuration.prototype.__defineGetter__("toolbar", function() { return this._toolbar; });

PrefBranch.defineConfigurationProperty(Configuration.prototype, "forumRoot", "");
PrefBranch.defineConfigurationProperty(Configuration.prototype, "memberRoot", "");
PrefBranch.defineConfigurationProperty(Configuration.prototype, "registeredVersion", "", "registered");
PrefBranch.defineConfigurationProperty(Configuration.prototype, "shownUpdates", "");
PrefBranch.defineConfigurationProperty(Configuration.prototype, "updatedPreferences", "");

Configuration.prototype._updates = [
    {
        oldPref: "extensions.namfox.toolbar.openLinksInNewTab",
        oldType: $("nsIPrefBranch").PREF_BOOL,
        newPref: "extensions.namfox.toolbar.linkBehavior",
        newType: $("nsIPrefBranch").PREF_INT,
        translate: function(oldPrefValue) {
            if (oldPrefValue) {
                // Open links in new tabs unconditionally
                return 2;
            }
            
            // Open on middle click.
            return 1;
        }
    },
    {
        oldPref: "extensions.namfox.posts.stripColors",
        oldType: $("nsIPrefBranch").PREF_BOOL,
        newPref: "extensions.namfox.stripMarkup.stripColors",
    },
    {
        oldPref: "extensions.namfox.stripMarkup.stripColors",
        oldType: $("nsIPrefBranch").PREF_BOOL,
        newPref: "extensions.namfox.stripMarkup.stripOptions",
        newType: $("nsIPrefBranch").PREF_INT,
        translate: function(oldPrefValue) {
            if (oldPrefValue) {
                return 1;
            }
            
            return 0;
        }
    }
];

Configuration.prototype.isInTestMode = function() {
    var obj = {};
    var results = this._prefBranch.getChildList("", obj);

    return results.indexOf("forumRoot") !== -1;
};

Configuration.prototype.update = function() {
    /// <summary>
    /// Updates old preferences to new preferences.
    /// </summary>
    
    if (this.updatedPreferences === $.version) {
        return;
    }
    
    $.verbose("Updating configuration preferences...");
    
    var nsIPrefBranch = $("nsIPrefBranch");
    var root = this._prefService.getBranch(""); 
    
    for (var i = 0; i < this._updates.length; ++i) {
        var update = this._updates[i];
        
        if (root.prefHasUserValue(update.oldPref)) {
            $.assert(
                root.getPrefType(update.newPref) !== 0,
                "The new preference must exist in order to update it."
            );
            
            var oldValue;
            switch (update.oldType) {
                case nsIPrefBranch.PREF_BOOL:
                    oldValue = root.getBoolPref(update.oldPref);
                    setter = root.setBoolPref;
                    break;
                case nsIPrefBranch.PREF_INT:
                    oldValue = root.getIntPref(update.oldPref);
                    setter = root.setIntPref;
                    break;
                case nsIPrefBranch.PREF_STRING:
                    oldValue = root.getCharPref(update.oldPref);
                    setter = root.setCharPref;
                    break;
            }
            
            var setter;
            switch (update.newType || update.oldType) {
                case nsIPrefBranch.PREF_BOOL:
                    setter = root.setBoolPref;
                    break;
                case nsIPrefBranch.PREF_INT:
                    setter = root.setIntPref;
                    break;
                case nsIPrefBranch.PREF_STRING:
                    setter = root.setCharPref;
                    break;
            }
            
            var newValue;
            if (update.translate) {
                newValue = update.translate(oldValue);
            }
            else {
                newValue = oldValue;
            }
            
            setter(update.newPref, newValue);
            root.clearUserPref(update.oldPref);
        }
    }
    
    this.updatedPreferences = $.version;
};

Configuration.prototype.QueryInterface = function(iid) {
    /// <summary>Provides runtime type discovery.</summary>
    /// <param name="iid" type="nsIIDRef">
    ///   The requested IID of the interface to try cast to.
    /// </param>
    
    if (!iid.equals($("nfxIConfiguration")) && !iid.equals($("nfxIPrefBranch")) && !iid.equals($("nsISupports"))) {
        throw Components.results.NS_ERROR_NO_INTERFACE;
    }
    
    return this;
}

/***********************************************************
  Tracing
***********************************************************/

function Trace() { // implements nfxITrace
    $.trace = this;
    $.assert = function(condition, message) {
        $.trace.assert(condition, message);
    };
    $.error = function(e, stackTrace) {
        $.trace.error(e, stackTrace);
    };
    $.info = function(message) {
        $.trace.info(message);
    };
    $.verbose = function(message) {
        $.trace.verbose(message);
    };
    this._diagnostics = $.config.diagnostics;
    this._assertUIEnabled = this._diagnostics.assertUIEnabled;
    this._exceptionUIEnabled = this._diagnostics.exceptionUIEnabled;
    this._diagnostics.addObserver("", this, false);
    
    this.traceLevel = this._diagnostics.traceLevel;
}

Trace.Off = 0;
Trace.TraceError = 1;
Trace.TraceWarning = 2;
Trace.TraceInfo = 3;
Trace.TraceVerbose = 4;

Trace.prototype = {
    _assertUIEnabled: false,
    _console: $("@mozilla.org/consoleservice;1").service("nsIConsoleService"),
    _exceptionUIEnabled: false,
    traceLevel: 0,
    
    _getStackTrace: function(initialFrame) {
        /// <summary>
        ///   Gets the stack trace from the current method.
        /// </summary>
        /// <param name="initialFrame" optional="true" type="nsIStackFrame">
        ///   (Optional) The stack frame from which to build the stack trace.
        /// </param>
        
        var frame = initialFrame || Components.stack.caller;
        var stackTrace = "";
        
        while (frame) {
            var fileName = frame.filename || "[anonymous]";
            stackTrace += "\n" + fileName + "@" + frame.lineNumber;
            frame = frame.caller;
        }
        
        return stackTrace;
    },
    
    _logError: function(exception, errorLevel, stackTrace) {
        /// <summary>
        ///   Logs an error to the Fx console with the specified error level.
        /// </summary>
        /// <param name="exception" type="nsIException">
        ///   The exception to log.
        /// </param>
        /// <param name="errorLevel" type="Number" integer="true">
        ///   The error level. One of the flag constants from nsIScriptError.
        /// </param>
    
        var error = $("@mozilla.org/scripterror;1").instance("nsIScriptError");
        var fileName = exception.filename || exception.fileName; // Fx 3 uses filename, while Fx 2 uses fileName.
        
        error.init(
            "NAMFox Exception: " + exception.toString(),
            fileName === undefined ? "[anonymous]" : fileName.replace(/chrome:\/\/namfox/, "namfox"),
            null,
            exception.lineNumber,
            0,
            errorLevel,
            "content javascript"
        );
        
        if (this._exceptionUIEnabled) {
            $.window().alert("NAMFox: " + exception.message);
        }
        
        this._console.logMessage(error);
        
        if (stackTrace) {
            this._logMessage("Stack trace for previous exception: " + stackTrace);
        }
        else {
            this._logMessage("Stack trace for previous exception's handler: " + this._getStackTrace(exception.location));
        }
    },
    
    _logMessage: function(message) {
        /// <summary>Logs a message string directly to the Firefox console.</summary>
        /// <param name="message" type="string">The message to log.</param>
    
        this._console.logStringMessage("NAMFox: " + message);
        dump(message);
    },
    
    assert: function(condition, message) {
        /// <summary>Checks for a condition and pops up an alert window if that condition is false.</summary>
        /// <param name="condition" type="boolean">True to prevent a message being displayed; otherwise, false.</param>
        /// <param name="message" type="string">A message to display.</param>
        
        if (!condition) {
            this.fail(message);
        }
    },
    
    dumpStackTrace: function() {
        /// <summary>
        ///   Dumps the current stack trace to the console.
        /// </summary>
        
        this._logMessage(this._getStackTrace());
    },
    
    error: function(e, stackTrace) {
        /// <summary>Logs an exception to the JS Console.</summary>
        /// <param name="e" type="nsIException">The exception to log.</param>
        /// <param name="stackTrace" type="String">
        ///   The stack trace of the exception.
        /// </param>
        
        if (this.traceLevel >= Trace.TraceError) {
            this._logError(e, $("nsIScriptError").exceptionFlag, stackTrace);
        }
    },
    
    fail: function(message) {
        /// <summary>Emits the specified error message.</summary>
        /// <param name="message" type="string">A message to display.</param>
        
        message += "\n\n" + this._getStackTrace();
        
        if (this._assertUIEnabled) {
            $.window().alert("Assertion Failed: " + message);
        }
        
        this._logMessage(message);
    },
    
    info: function(message) {
        /// <summary>
        ///   Logs a message to the JS Console and the dump window
        ///   as an informational message.
        /// </summary>
        /// <param name="message" type="String">The message to write.</param>
        
        if (this.traceLevel >= Trace.TraceInfo) {
            this._logMessage("INFO : " + message);
        }
    },
    
    observe: function(subject, topic, data) {
        /// <summary>
        ///   Called when there is a notification for the topic
        ///   that the observer has been registered for.
        /// </summary>
        /// <param name="subject" type="nsISupports">
        ///   The object whose change or action is being observed.
        /// </param>
        /// <param name="topic" type="String">
        ///   Indicates the specific change or action.</param>
        /// <param name="data" type="String" optional="true">
        ///   (Optional) Auxiliary data further describing the change or action.
        /// </param>
        
        if (topic !== "nsPref:changed") {
            return;
        }
        
        switch (data) {
            case "assertUIEnabled":
                this._assertUIEnabled = this._diagnostics.assertUIEnabled;
                break;
            case "exceptionUIEnabled":
                this._exceptionUIEnabled = this._diagnostics.exceptionUIEnabled;
                break;
            case "traceLevel":
                this.traceLevel = this._diagnostics.traceLevel;
                break;
        }
    },
    
    verbose: function(message) {
        /// <summary>
        ///   Logs a message to the JS Console and the dump
        ///   window as a verbose message.
        /// </summary>
        /// <param name="message" type="String">The message to write.</param>
        
        if (this.traceLevel >= Trace.TraceVerbose) {
            this._logMessage("VERBOSE : " + message);
        }
    },

    warn: function(e, stackTrace) {
        /// <summary>
        ///   Logs an exception to the JS Console as a warning.
        /// </summary>
        /// <param name="exception" type="nsIException">
        ///   The exception to log.
        /// </param>
        /// <param name="stackTrace" type="String">
        ///   The stack trace of the exception.
        /// </param>
        
        if (this.traceLevel >= Trace.TraceWarning) {
            this._logError(e, $("nsIScriptError").warningFlag, stackTrace);
        }
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxITrace")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  Text Transformation
***********************************************************/

function GamespotUrlTransformer() { // implements nfxITextTransformer
}

GamespotUrlTransformer.prototype = {
    transform: function(s) {
        return s.replace(/^.*?\/(\d+)\/\D.*?$/, "$1");
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxITextTransformer")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};


function GametrailersUrlTransformer() { // implements nfxITextTransformer
}

GametrailersUrlTransformer.prototype = {
    transform: function(s) {
        return s.replace(/.*?player\/(\d+).*?$/, "$1");
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxITextTransformer")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

function GametrailersUserUrlTransformer() { // implements nfxITextTransformer
}

GametrailersUserUrlTransformer.prototype = {
    transform: function(s) {
        return s.replace(/^.*?usermovies\/(\d+).*?$/, "$1");
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxITextTransformer")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

function GamevideosUrlTransformer() { // implements nfxITextTransformer
}

GamevideosUrlTransformer.prototype = {
    transform: function(s) {
        return s.replace(/^.*?id\/(\d+).*?$/, "$1");
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxITextTransformer")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};


function YoutubeUrlTransformer() { // implements nfxITextTransformer
}

YoutubeUrlTransformer.prototype = {
    transform: function(s) {
        return s.replace(/^.*?watch\?v=(.*?)(?:\&.*?)?$/, "$1");
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxITextTransformer")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  Encoder
***********************************************************/

function Encoder() { // implements nfxIEncoder
}

Encoder.prototype = {
    attrEncode: function(s) {
        /// <summary>
        /// Encodes a string so that it can be used in an HTML literal as an attribute.
        /// </summary>
        /// <param name="s">The string to encode.</param>
        
        return s.replace(/"/g, "&quot;");
    },
    
    urlEncode: function(s) {
        /// <summary>
        /// Encodes a string so that it can be sent in a URL.
        /// </summary>
        /// <param name="s">The string to encode.</param>
        
        var encodedString = "";
        var unicodeConverter = $("@mozilla.org/intl/scriptableunicodeconverter").
                instance("nsIScriptableUnicodeConverter");
        unicodeConverter.charset = "UTF-8";
        for (var i = 0; i < s.length; ++i) {
            var code = s.charCodeAt(i);
            
            if (code > 127) {
                var buffer = {}, bufferLength = {};
                var result = unicodeConverter.convertToByteArray(s[i], bufferLength, buffer);
                
                for (var j = 0; j < result.length; ++j) {
                    var codeString = result[j].toString(16);
                    encodedString += "%" + (codeString.length === 1 ? "0" + codeString : codeString);
                }
            }
            else if (code > 63) {
                encodedString += s[i];
            }
            else if (code === 10) {
                if (i > 0 && s[i - 1] === "\r") {
                    encodedString += "%0a";
                }
                else {
                    encodedString += "%0d%0a";
                }
            }
            else if (code === 32) {
                encodedString += "+";
            }
            else {
                var codeString = code.toString(16);
                encodedString += "%" + (codeString.length === 1 ? "0" + codeString : codeString);
            }
        }
        
        return encodedString;
    },
    
    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIEncoder")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  URL Resolution Services
***********************************************************/

function UrlResolver() { // implements nfxIUrlResolver
    $.url = this;
}

UrlResolver.prototype = {
    api: function(path) {
        /// <summary>
        /// Resolves a URL to the Neoseeker API, given the specified URI path.
        /// </summary>
        /// <param name="path">The path of the URI, to be combined with the root
        /// for the API.</param>
        
#ifdef DEV
        return "http://api.beta.neoseeker.com/" + $.removeLeadingForwardSlash(path);
#else
        return "http://api.neoseeker.com/" + $.removeLeadingForwardSlash(path);
#endif
    },
    
    blog: function(path) {
        /// <summary>
        /// Resolves a URL to the blog pages, given the specified URI path.
        /// </summary>
        /// <param name="path">The path of the URI, to be combined with the root
        /// for blogs.</param>
        
#ifdef DEV
        return "http://blogs.beta.neoseeker.com/" + $.removeLeadingForwardSlash(path);
#else
        return "http://blogs.neoseeker.com/" + $.removeLeadingForwardSlash(path);
#endif
    },

    forum: function(path) {
        /// <summary>
        /// Resolves a URL to the forum pages, given the specified URI path.
        /// </summary>
        /// <param name="path">The path of the URI, to be combined with the root
        /// for forums.</param>
        
#ifdef DEV
        return "http://www.beta.neoseeker.com/forums/" + $.removeLeadingForwardSlash(path);
#else
        return "http://www.neoseeker.com/forums/" + $.removeLeadingForwardSlash(path);
#endif
    },
    
    member: function(path) {
        /// <summary>
        /// Resolves a URL to the member pages, given the specified URI path.
        /// </summary>
        /// <param name="path">The path of the URI, to be combined with the root
        /// for members.</param>
        
#ifdef DEV
        return "http://www.beta.neoseeker.com/members/" + $.removeLeadingForwardSlash(path);
#else
        return "http://www.neoseeker.com/members/" + $.removeLeadingForwardSlash(path);
#endif
    },

    QueryInterface: function(iid) {
        /// <summary>Provides runtime type discovery.</summary>
        /// <param name="iid" type="nsIIDRef">
        ///   The requested IID of the interface to try cast to.
        /// </param>
        
        if (!iid.equals($("nfxIUrlResolver")) && !iid.equals($("nsISupports"))) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        
        return this;
    }
};

/***********************************************************
  NAMFox Bootstrapper (not exposed over XPCOM)
***********************************************************/

function NAMFoxBootstrapper() {
}

NAMFoxBootstrapper.prototype = {
    observe: function(subject, topic, data) {
        /// <summary>
        ///   Called when there is a notification for the topic
        ///   that the observer has been registered for.
        /// </summary>
        /// <param name="subject" type="nsISupports">
        ///   The object whose change or action is being observed.
        /// </param>
        /// <param name="topic" type="String">
        ///   Indicates the specific change or action.</param>
        /// <param name="data" type="String" optional="true">
        ///   (Optional) Auxiliary data further describing the change or action.
        /// </param>
        
        if (topic === "quit-application") {
            $._observerService.removeObserver(this, "neoseeker-user-name-changed");
            $._observerService.removeObserver(this, "quit-application");
        }
        else if (topic === "neoseeker-user-name-changed") {
            var member = subject.QueryInterface($("nfxIMember"));
            
            // The presence of a userName ensures the user is logged in.
            if (member && member.userName) {
                var configuration = $.config;
                
                $.verbose(configuration.registeredVersion + " !== " + $.version);
                if (configuration.registeredVersion !== $.version) {
                    var that = this;
                    $.post(
                        $.url.forum(),
                        {
                            wrappedJSObject: {
                                fn: "send_pm_thread",
                                manual_username: "Artificer",
                                title: "NAMFox+" + $.version + "+Registration",
                                submit: "submit",
                                message: $.window().navigator.userAgent
                            }
                        },
                        function (xhr) {
                            if (xhr.responseText.indexOf("Private Message Sent") !== -1) {
                                configuration.registeredVersion = $.version;
                            }
                        },
                        null,
                        $("nfxIAjax").UseCache
                    );
                }
            }
        }
    },
    
    start: function() {
        $("@mozilla.org/consoleservice;1").service("nsIConsoleService").logStringMessage("Adding NAMFox Bootstrapper observers.");
        $._observerService.addObserver(this, "neoseeker-user-name-changed", false);
        $._observerService.addObserver(this, "quit-application", false);
    }
};

/***********************************************************
class factories
***********************************************************/
function createFactory (ctor, isSingleton) {
    var singletonCreateInstance = {
        singleton: null,
        createInstance: function(outer, iid) {
            if (outer != null) {
                throw Components.results.NS_ERROR_NO_AGGREGATION;
            }
            if (!this.singleton) {
                this.singleton = new ctor();
            }
            return this.singleton.QueryInterface(iid);
        }
    };
    function normalCreateInstance(outer, iid) {
        if (outer !== null) {
            throw Components.results.NS_ERROR_NO_AGGREGATION;
        }
        return (new ctor()).QueryInterface(iid);
    }
    if (isSingleton) {
        return { createInstance: singletonCreateInstance.createInstance };
    }
    else {
        return { createInstance: normalCreateInstance };
    }
}

/***********************************************************
module definition (xpcom registration)
***********************************************************/
var NAMFoxModule = {
    objects: {
        trace: {
            CID         : Components.ID("{1C6865F4-A503-46E6-ABEC-1DB882D3821A}"),
            contractID  : "@namfox.neoseeker.com/trace;1",
            className   : "NAMFox Tracer",
            factory     : createFactory(Trace, true)
        },
        dictionary: {
            CID         : Components.ID("{CAB32AE5-92D1-48A8-BAC6-9150B3B1F133}"),
            contractID  : "@namfox.neoseeker.com/dictionary;1",
            className   : "NAMFox Dictionary",
            factory     : createFactory(Dictionary, false)
        },
        dataRepository: {
            CID         : Components.ID("{10250359-3EDF-4563-B1F8-3D72E27CE1C6}"),
            contractID  : "@namfox.neoseeker.com/data-repository;1",
            className   : "NAMFox Data Repository",
            factory     : createFactory(DataRepository, true)
        },
        ajax: {
            CID         : Components.ID("{1ECBB13E-E68F-4BBA-B3E7-70FBC909C23E}"),
            contractID  : "@namfox.neoseeker.com/ajax;1",
            className   : "NAMFox Ajax Utility",
            factory     : createFactory(Ajax, true)
        },
        message: {
            CID         : Components.ID("{B0DFF403-C315-427F-9197-531DC219EEBB}"),
            contractID  : "@namfox.neoseeker.com/custom-messages/message;1",
            className   : "Custom Message",
            factory     : createFactory(CustomMessage, false)
        },
        namedMessage: {
            CID         : Components.ID("{D8A9A9D1-DCC5-4D03-8576-27068DB90E63}"),
            contractID  : "@namfox.neoseeker.com/custom-message/named-message;1",
            className   : "Named Custom Message",
            factory     : createFactory(NamedCustomMessage, false)
        },
        messageManager: {
            CID         : Components.ID("{98100857-5AF7-4BB0-AAFD-BE7E494CF8BC}"),
            contractID  : "@namfox.neoseeker.com/custom-messages/manager;1",
            className   : "Custom Message Manager",
            factory     : createFactory(CustomMessageManager, true)
        },
        member: {
            CID         : Components.ID("{E08B1CE7-6701-4339-959C-614A44FEA6AD}"),
            contractID  : "@namfox.neoseeker.com/member;1",
            className   : "Neoseeker Member",
            factory     : createFactory(Member, false)
        },
        memberService: {
            CID         : Components.ID("{FA8FC00E-FDF5-4EEA-AF02-D54501FD5896}"),
            contractID  : "@namfox.neoseeker.com/member-service;1",
            className   : "Neoseeker Member Service",
            factory     : createFactory(MemberService, true)
        },
        prefUpdater: {
            CID         : Components.ID("{C1F17874-1AA8-4a91-9EC5-E11FEAA39633}"),
            contractID  : "@namfox.neoseeker.com/pref-updater;1",
            className   : "NAMFox Preference Updater",
            factory     : createFactory(PrefUpdater, true)
        },
        configuration: {
            CID         : Components.ID("{FFBE377C-075A-4CFB-A5C0-73CE588DE578}"),
            contractID  : "@namfox.neoseeker.com/configuration;1",
            className   : "NAMFox Configuration",
            factory     : createFactory(Configuration, true)
        },
        gamespotTransformer: {
            CID         : Components.ID("{B3F82D6E-19D3-4C49-8FC1-59AF8863583D}"),
            contractID  : "@namfox.neoseeker.com/transformers/gamespot;1",
            className   : "Text Transformer for Gamespot Video URLs",
            factory     : createFactory(GamespotUrlTransformer, true)
        },
        gametrailersTransformer: {
            CID         : Components.ID("{02999C81-B8B3-4811-9774-B877FFC05717}"),
            contractID  : "@namfox.neoseeker.com/transformers/gametrailers;1",
            className   : "Text Transformer for Gametrailers Video URLs",
            factory     : createFactory(GametrailersUrlTransformer, true)
        },
        gametrailersUserTransformer: {
            CID         : Components.ID("{3C9C8E10-BD66-495E-BD37-9490165C6AF6}"),
            contractID  : "@namfox.neoseeker.com/transformers/gametrailers-user;1",
            className   : "Text Transformer for Gametrailers User Video URLs",
            factory     : createFactory(GametrailersUserUrlTransformer, true)
        },
        gamevideosTransformer: {
            CID         : Components.ID("{C26850B0-2F4D-4395-88EC-E900A344DC9F}"),
            contractID  : "@namfox.neoseeker.com/transformers/gamevideos;1",
            className   : "Text Transformer for Gamevideos Video URLs",
            factory     : createFactory(GamevideosUrlTransformer, true)
        },
        youtubeTransformer: {
            CID         : Components.ID("{10E97131-F0E2-4941-B92E-B9934F57746D}"),
            contractID  : "@namfox.neoseeker.com/transformers/youtube;1",
            className   : "Text Transformer for Youtube Video URLs",
            factory     : createFactory(YoutubeUrlTransformer, true)
        },
        encoder: {
            CID         : Components.ID("{26C6C83B-7DB2-4A32-889F-21914E7D8CAD}"),
            contractID  : "@namfox.neoseeker.com/encoder;1",
            className   : "Encoder",
            factory     : createFactory(Encoder, true)
        },
        urlResolver: {
            CID         : Components.ID("{8D6AA68F-9D82-4DF9-AA4F-7DA54DD854E2}"),
            contractID  : "@namfox.neoseeker.com/urlResolver;1",
            className   : "UrlResolver",
            factory     : createFactory(UrlResolver, true)
        }
    },
    registerSelf: function(componentManager, fileSpec, location, type) {
        componentManager = componentManager.QueryInterface($("nsIComponentRegistrar"));
                
        for (var key in this.objects) {
            var value = this.objects[key];
            componentManager.registerFactoryLocation(value.CID, value.className, value.contractID, fileSpec, location, type);
        }
    },
    getClassObject: function(componentManager, cid, iid) {
        if (!iid.equals($("nsIFactory"))) {
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        }

        return this.getFactory(cid);
    },

    getFactory: function(cid) {
        for (var key in this.objects) {
            if (cid.equals(this.objects[key].CID)) {
                return this.objects[key].factory;
            }
        }

        throw Components.results.NS_ERROR_NO_INTERFACE;
    },

    unregisterSelf: function(componentManager, location, type) {
        componentManager = componentManager.QueryInterface($("nsIComponentRegistrar"));
                
        for (var key in this.objects) {
            var value = this.objects[key];
            componentManager.unregisterFactoryLocation(value.CID, location);
        }
    },
    canUnload: function(aCompMgr) {
        return true;
    }
};

/***********************************************************
module initialization

When the application registers the component, this function
is called.
***********************************************************/

// This is only called by Firefox 4.0 Beta 1 and below.
function NSGetModule(componentManager, fileSpec) {
    try {
        new NAMFoxBootstrapper().start();
    }
    catch (e) {
        var consoleService = $("@mozilla.org/consoleservice;1").service("nsIConsoleService");
        consoleService.logStringMessage("ERROR in NAMFoxBootstrapper initialization.");
        consoleService.logStringMessage(e.toString());
    }
    
    return NAMFoxModule;
}

// This is only called by Firefox 4.0 Beta 2 and above.
var initializedBootstrapper = false;
function NSGetFactory(cid) {
    if (!initializedBootstrapper) {
        initializedBootstrapper = true;
        try {
            new NAMFoxBootstrapper().start();
        }
        catch (e) {
            var consoleService = $("@mozilla.org/consoleservice;1").service("nsIConsoleService");
            consoleService.logStringMessage("ERROR in NAMFoxBootstrapper initialization.");
            consoleService.logStringMessage(e.toString());
        }
    }
    
    return NAMFoxModule.getFactory(cid);
}